<?php

namespace App\Http\Validate;

use App\Exceptions\CustomValidateException;
use Closure;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\MessageBag;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use Kuafu\File\FileUtil;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;

abstract class AbstractBaseValidate
{
    /**
     * @var Closure|null
     */
    public ?Closure $beforeValidateClosure = null;

    /**
     * 校验基础数据
     *
     * @var array
     */
    private array $data;


    /**
     * 校验实际规则
     *
     * @var array
     */
    private array $validateRules;

    /**
     * @param Closure $closure
     * @return self
     */
    protected function registerBeforeValidateClosure(Closure $closure): self
    {
        $this->beforeValidateClosure = $closure;
        return $this;
    }


    /**
     * 获取校验规则
     *
     * @return array
     */
    abstract function getRules(): array;


    /**
     * 获取校验属性
     *
     * @return array
     */
    abstract function getAttributes(): array;


    /**
     *  注册校验规则
     */
    abstract function registerValidateRules(): void;

    /**
     * 获取校验错误信息
     *
     * @return array
     */
    abstract function getMessages(): array;

    private final function getValidateRules($fields = []): array
    {
        $rules = $this->getRules();
        return empty($fields) ? $rules : (new Collection($rules))->filter(function ($v, $k) use ($fields) {
            if (in_array($k, $fields)) {
                return true;
            }
            return false;
        })->toArray();
    }


    /**
     * @return AbstractBaseValidate
     */
    protected final static function getInstance(): AbstractBaseValidate
    {
        return new static();
    }

    /**
     * @param array $fields
     * @param array|null $data
     * @param array|null $extraRules
     * @param bool $returnParams
     * @param Closure|null $handleParams
     * @param bool $isThrow
     * @return MessageBag|object
     * @throws ContainerExceptionInterface
     * @throws NotFoundExceptionInterface|CustomValidateException
     */
    public final function validate(
        array $fields = [],
        array $data = null,
        array $extraRules = null,
        bool $returnParams = true,
        Closure $handleParams = null,
        bool $isThrow = true
    ) {
        $this->registerValidateRules();
        if ($this->beforeValidateClosure) {
            $this->data = ($this->beforeValidateClosure)($data ?: app()->get("request")->all());
        } else {
            $this->data = $data ?: app()->get("request")->all();
        }

        $this->validateRules = array_merge($this->getValidateRules($fields), $extraRules ?? []);
        $validator           = Validator::make(
            $this->data,
            $this->validateRules,
            $this->getMessages(),
            $this->getAttributes()
        );


        if ($validator->fails()) {
            if ($isThrow) {
                throw new CustomValidateException($validator);
            } else {
                return $validator->errors();
            }
        }

        if ($returnParams) {
            return (object)$this->getParams($handleParams);
        }
    }

    /**
     * 校验身份证号是否合法
     * @param string $num 待校验的身份证号
     * @return bool
     */
    public static function isValid(string $num): bool
    {
        //老身份证长度15位，新身份证长度18位
        $length = strlen($num);
        if ($length == 15) { //如果是15位身份证

            //15位身份证没有字母
            if (!is_numeric($num)) {
                return false;
            }
            // 省市县（6位）
            $areaNum = substr($num, 0, 6);
            // 出生年月（6位）
            $dateNum = substr($num, 6, 6);

        } else {
            if ($length == 18) { //如果是18位身份证

                //基本格式校验
                if (!preg_match('/^\d{17}[0-9xX]$/', $num)) {
                    return false;
                }
                // 省市县（6位）
                $areaNum = substr($num, 0, 6);
                // 出生年月日（8位）
                $dateNum = substr($num, 6, 8);

            } else { //假身份证
                return false;
            }
        }

        //验证地区
        if (!self::isAreaCodeValid($areaNum)) {
            return false;
        }

        //验证日期
        if (!self::isDateValid($dateNum)) {
            return false;
        }

        //验证最后一位
        if (!self::isVerifyCodeValid($num)) {
            return false;
        }

        return true;
    }

    /**
     * 省市自治区校验
     * @param string $area 省、直辖市代码
     * @return bool
     */
    public static function isAreaCodeValid(string $area): bool
    {
        $provinceCode = substr($area, 0, 2);

        // 根据GB/T2260—999，省市代码11到65
        if (11 <= $provinceCode && $provinceCode <= 65) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 验证出生日期合法性
     * @param string $date 日期
     * @return bool
     */
    public static function isDateValid(string $date): bool
    {
        if (strlen($date) == 6) { //15位身份证号没有年份，这里拼上年份
            $date = '19' . $date;
        }
        $year  = intval(substr($date, 0, 4));
        $month = intval(substr($date, 4, 2));
        $day   = intval(substr($date, 6, 2));

        //日期基本格式校验
        if (!checkdate($month, $day, $year)) {
            return false;
        }

        //日期格式正确，但是逻辑存在问题(如:年份大于当前年)
        $currYear = date('Y');
        if ($year > $currYear) {
            return false;
        }
        return true;
    }

    /**
     * 验证18位身份证最后一位
     * @param string $num 待校验的身份证号
     * @return bool
     */
    public static function isVerifyCodeValid(string $num): bool
    {
        if (strlen($num) == 18) {
            $factor = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
            $tokens = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];

            $checkSum = 0;
            for ($i = 0; $i < 17; $i++) {
                $checkSum += intval($num[$i]) * $factor[$i];
            }

            $mod   = $checkSum % 11;
            $token = $tokens[$mod];

            $lastChar = strtoupper($num[17]);

            if ($lastChar != $token) {
                return false;
            }
        }
        return true;
    }


    /**
     * @param Closure|null $closure
     * @return array|mixed
     */
    private final function getParams(Closure $closure = null)
    {
        $realParams = [];
        $ruleKeys   = array_keys($this->validateRules);
        //获取验证参数 & 公有处理参数
        foreach ($this->data as $field => $value) {
            if (in_array($field, $ruleKeys)) {
                if (Str::endsWith(strtoupper($field), 'IMG')) {
                    $realParams[$field] = str_replace(FileUtil::domain(), '', $value);
                } else {
                    $realParams[$field] = $value;
                }
            }
        }

        //特殊参数闭包特殊处理
        if ($closure instanceof Closure) {
            return $closure($realParams);
        }

        return $realParams;
    }

    /**
     * @param array $enum
     * @param array $extraArr
     * @return string
     */
    public static function enumTransToRules(array $enum, array $extraArr = []): string
    {
        return implode(",", array_keys($enum)) . (empty($extraArr) ? '' : ',' . implode(",", $extraArr));
    }

    /**
     * @param bool $required
     * @param int $max
     * @param int $min
     * @param Closure|null $closure
     * @return array
     */
    public static function stringValidateBuild(bool $required, int $max, int $min = 0, Closure $closure = null): array
    {
        return array_merge(
            [
                $required ? "required" : "sometimes",
                "string",
                "min:{$min}",
                "max:{$max}"
            ],
            $closure ? [$closure()] : []
        );
    }

    /**
     * @param bool $required
     * @param int $max
     * @param int $min
     * @return array
     */
    public static function intValidateBuild(bool $required, int $max, int $min = 0): array
    {
        return [
            $required ? "required" : "sometimes",
            "int",
            "min:{$min}",
            "max:{$max}"
        ];
    }

    /**
     * @param bool $required
     * @param array $enum
     * @return array
     */
    public static function intInValidateBuild(bool $required, array $enum): array
    {
        return [
            $required ? "required" : "sometimes",
            "int",
            "in:" . self::enumTransToRules($enum)
        ];
    }

    /**
     * @param bool $required
     * @param array $enum
     * @return array
     */
    public static function stringInValidateBuild(bool $required, array $enum): array
    {
        return [
            $required ? "required" : "sometimes",
            "string",
            "in:" . self::enumTransToRules($enum)
        ];
    }
}
